/*
 * sso
 */
package com.opensso.login.service;

import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWTCreator;
import com.baidu.fsg.uid.UidGenerator;
import com.opensso.account.entity.Account;
import com.opensso.core.entity.BeanRet;
import com.opensso.core.enums.SessionEnum;
import com.opensso.core.exceptions.BaseException;
import com.opensso.core.exceptions.DomainPoolException;
import com.opensso.core.exceptions.SessionException;
import com.opensso.core.exceptions.SettingException;
import com.opensso.core.tools.HttpTools;
import com.opensso.core.tools.JwtToken;
import com.opensso.core.tools.SessionTools;
import com.opensso.core.tools.redis.RedisUtils;
import com.opensso.domain.dao.DomainLogDAO;
import com.opensso.domain.dao.DomainPoolDAO;
import com.opensso.domain.entity.DomainLog;
import com.opensso.domain.entity.DomainPool;
import com.opensso.sessioin.service.SessionSV;
import com.opensso.setting.dao.SettingDAO;
import com.opensso.setting.entity.Setting;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 会话业务层
 */
@Service("sessionSV")
@Slf4j
public class SessionSVImpl implements SessionSV {


    @Resource
    private UidGenerator uidGenerator;

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private SettingDAO settingDAO;

    @Resource
    private DomainLogDAO domainLogDAO;

    @Resource
    private DomainPoolDAO domainPoolDAO;

    /**
     * 创建会话
     * 1.验证账户密码合法性
     * 2.生成会话
     * 3.记录日志
     *
     * @param account  账户
     * @param password 密码
     * @return token
     */
    @Override
    public String build(String account, String password, String clientIp, String serverIp, String language, String requestUrl, String domain) {
        //1.验证账户密码合法性
        Setting setting = settingDAO.loadByK(Setting.Key.Check_Account_Url.name());
        BeanRet beanRet;
        if (setting != null && StringUtils.isNotBlank(setting.getV())) {
            String url = setting.getV().replace("{account}", account).replace("{password}", password);
            beanRet = HttpTools.Instance.get(url);
        } else {
            throw new SettingException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }
        Account accountObj = null;
        if (beanRet == null || !beanRet.isSuccess()) {
            throw new SettingException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }
        accountObj = JSON.parseObject(beanRet.getData().toString(), Account.class);
        if (accountObj == null) {
            throw new SettingException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }
        //2.生成会话
        Setting session_Expire_TimeSetting = settingDAO.loadByK(Setting.Key.Session_Expire_Time.name());//会话过期时间
        int session_Expire_Time = Integer.parseInt(session_Expire_TimeSetting.getV());
        Setting token_Expire_TimeSetting = settingDAO.loadByK(Setting.Key.Token_Expire_Time.name());//token过期时间
        long token_Expire_Time = Long.parseLong(token_Expire_TimeSetting.getV());

        String sessionkey = SessionTools.Instance.genSessionKey(account);
        JWTCreator.Builder builder = JwtToken.create();
        builder.withClaim(SessionEnum.ClientIp.name(), clientIp)
                .withClaim(SessionEnum.SessionKey.name(), sessionkey)
                .withClaim(SessionEnum.Code.name(), accountObj.getCode())
                .withClaim(SessionEnum.Account.name(), account)
                .withClaim(SessionEnum.Language.name(), language)
                .withClaim(SessionEnum.RefreshToken.name(), SessionEnum.RefreshToken.name())
                .withClaim(SessionEnum.Session_Expire_Time.name(), String.valueOf(session_Expire_Time));

        //生成token 给cookie使用 或者前端缓存使用
        Setting token_SecretSetting = settingDAO.loadByK(Setting.Key.Token_Secret.name());//refresh_token过期时间

        String refreshToken = JwtToken.createToken(builder, token_SecretSetting.getV(), token_Expire_Time);
        redisUtils.set(sessionkey, refreshToken);
        //3.记录日志
        DomainLog domainLog = new DomainLog(String.valueOf(uidGenerator.getUID()), account, clientIp, serverIp, language, requestUrl, domain, null);
        domainLogDAO.insert(domainLog);
        return refreshToken;
    }

    /**
     * 刷新token
     * 1.验证账户密码合法性
     * 2.生成会话
     * 3.记录日志
     *
     * @param account    账户
     * @param sessionKey sessionKey
     * @param clientIp   客户端ip
     * @param serverIp   服务端ip
     * @param language   语言
     * @param requestUrl 请求地址
     * @param apiKey     apikey
     * @return token
     */
    @Override
    public String refresh(String account, String sessionKey, String clientIp, String serverIp, String language, String requestUrl, String apiKey) {
        //1.验证账户密码合法性
        if (redisUtils.hasNotKey(sessionKey)) {
            log.error(BaseException.BaseExceptionEnum.Result_Not_Exist.toString());
            throw new SessionException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }
        Setting setting = settingDAO.loadByK(Setting.Key.Load_Account_Url.name());
        BeanRet beanRet = null;
        if (setting != null && StringUtils.isNotBlank(setting.getV())) {
            beanRet = HttpTools.Instance.get(setting.getV().replace("{account}", account));
        } else {
            throw new SettingException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }
        Account accountObj = null;
        if (beanRet != null && beanRet.isSuccess()) {
            accountObj = JSON.parseObject(beanRet.getData().toString(), Account.class);
        }

        Map<String, Object> param = new HashMap<>();
        param.put("apiKey", apiKey);
        DomainPool domainPool = domainPoolDAO.load(param);
        if (domainPool == null) {
            log.error(BaseException.BaseExceptionEnum.Result_Not_Exist.toString());
            throw new DomainPoolException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }

        //2.生成会话
        Setting session_Expire_TimeSetting = settingDAO.loadByK(Setting.Key.Session_Expire_Time.name());//会话过期时间
        int session_Expire_Time = Integer.parseInt(session_Expire_TimeSetting.getV());
        Setting token_Expire_TimeSetting = settingDAO.loadByK(Setting.Key.Token_Expire_Time.name());//token过期时间
        long token_Expire_Time = Long.parseLong(token_Expire_TimeSetting.getV());


        JWTCreator.Builder builder = JwtToken.create();
        builder.withClaim(SessionEnum.SessionKey.name(), sessionKey)
                .withClaim(SessionEnum.ClientIp.name(), clientIp)
                .withClaim(SessionEnum.Code.name(), accountObj.getCode())
                .withClaim(SessionEnum.Account.name(), account)
                .withClaim(SessionEnum.Language.name(), language)
                .withClaim(SessionEnum.Session_Expire_Time.name(), String.valueOf(session_Expire_Time));

        //生成token 给cookie使用 或者前端缓存使用
        String token = JwtToken.createToken(builder, domainPool.getApiSecret(), token_Expire_Time);

        redisUtils.set(sessionKey, JSON.toJSONString(accountObj), session_Expire_Time);
        //3.记录日志
        DomainLog domainLog = new DomainLog(domainPool.getCode(), account, clientIp, serverIp, language, requestUrl, domainPool.getDomain(), null);
        domainLogDAO.insert(domainLog);
        return token;
    }


    /**
     * 刷新token
     * 1.获取已经登录的所有系统
     * 2.注销所有登录的系统
     * 3.记录日志
     *
     * @param account    账户
     * @param sessionKey sessionKey
     * @param clientIp   客户端ip
     * @param serverIp   服务端ip
     * @param language   语言
     * @param requestUrl 请求地址
     * @param apiKey     apikey
     * @return true/false
     */
    @Override
    public boolean invalidate(String account, String sessionKey, String clientIp, String serverIp, String language, String requestUrl, String apiKey) {
        if (redisUtils.hasNotKey(sessionKey)) {
            return true;
        }
        Map<String, Object> param = new HashMap<>();
        param.put("apiKey", apiKey);
        DomainPool domainPool = domainPoolDAO.load(param);
        if (domainPool == null) {
            log.error(BaseException.BaseExceptionEnum.Result_Not_Exist.toString());
            throw new DomainPoolException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }

        //1.获取已经登录的所有系统
        String regDomainPoolKey = SessionTools.Instance.genRegDomainPoolKey(sessionKey);
        List<DomainPool> domainPools = null;
        if (redisUtils.hasKey(regDomainPoolKey)) {
            String json = redisUtils.get(regDomainPoolKey).toString();
            domainPools = JSON.parseArray(json, DomainPool.class);
        }

        if (domainPools == null || domainPools.isEmpty()) {
            log.error(BaseException.BaseExceptionEnum.Session_Error.toString());
            throw new SessionException(BaseException.BaseExceptionEnum.Session_Error);
        }

        //2.注销所有登录的系统
        //注销缓存
        redisUtils.del(sessionKey);
        //注销各个系统的局部session
        Setting token_Expire_TimeSetting = settingDAO.loadByK(Setting.Key.Token_Expire_Time.name());//token过期时间
        long token_Expire_Time = Long.parseLong(token_Expire_TimeSetting.getV());

        Setting setting = settingDAO.loadByK(Setting.Key.Load_Account_Url.name());
        BeanRet beanRet;
        if (setting != null && StringUtils.isNotBlank(setting.getV())) {
            beanRet = HttpTools.Instance.get(setting.getV().replace("{account}", account));
        } else {
            log.error(BaseException.BaseExceptionEnum.Result_Not_Exist.toString());
            throw new SettingException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }
        Account accountObj = null;
        if (beanRet != null && beanRet.isSuccess()) {
            accountObj = JSON.parseObject(beanRet.getData().toString(), Account.class);
        }
        String code = accountObj.getCode();
        JWTCreator.Builder builder = JwtToken.create();
        builder.withClaim(SessionEnum.SessionKey.name(), sessionKey)
                .withClaim(SessionEnum.ClientIp.name(), clientIp)
                .withClaim(SessionEnum.Code.name(), code)
                .withClaim(SessionEnum.Account.name(), account)
                .withClaim(SessionEnum.Invalidate.name(), account)
                .withClaim(SessionEnum.Language.name(), language);

        domainPools.forEach(domainPoolObj -> {
            String token = JwtToken.createToken(builder, domainPool.getApiSecret(), token_Expire_Time);

            Setting invalidate_setting = settingDAO.loadByK(Setting.Key.Invalidate_Url.name());
            if (invalidate_setting != null && StringUtils.isNotBlank(invalidate_setting.getV())) {
                String url = invalidate_setting.getV().replace("{Domain}", domainPoolObj.getDomain());
                HttpTools.Instance.invalidate(url, token);
            } else {
                log.error(BaseException.BaseExceptionEnum.Result_Not_Exist.toString());
                throw new SettingException(BaseException.BaseExceptionEnum.Result_Not_Exist);
            }
            //3.记录日志
            DomainLog domainLog = new DomainLog(domainPoolObj.getCode(), account, clientIp, serverIp, language, requestUrl, domainPoolObj.getDomain(), null);
            domainLogDAO.insert(domainLog);
        });

        redisUtils.del(regDomainPoolKey);
        return true;
    }


}
